package com.bruce.tool.logger.rabbit.aop;

import com.alibaba.fastjson.JSON;
import com.bruce.tool.common.util.DateUtils;
import com.bruce.tool.logger.rabbit.domain.MessageInfo;
import com.bruce.tool.logger.rabbit.domain.MessageStatusEnum;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.UUID;

/**
 * 功能 :
 * @author : Bruce(刘正航) 9:46 上午 2019/10/21
 */
@Aspect
public class SpringAspectLogger {

    private static Logger logger = LoggerFactory.getLogger(SpringAspectLogger.class);
    public static final ThreadLocal<String> MESSAGE_THREADLOCAL = new ThreadLocal<>();

    @Pointcut("execution(* org.springframework.amqp.core.AmqpTemplate.convertAndSend(java.lang.String, java.lang.String, java.lang.Object))")
    private void pointSend(){
    }

    @Around("pointSend()")
    public Object aroundSend(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] params = joinPoint.getArgs();
        if(null == params){ return joinPoint.proceed(); }
        if(params.length!=3){ return joinPoint.proceed(); }
        String exchange = (String) params[0];
        String queue = (String) params[1];
        Object message = params[2];
        String messageInfo = JSON.toJSONString(message);
        String messageId = MESSAGE_THREADLOCAL.get();
        if(StringUtils.isBlank(messageId)){
            messageId = UUID.randomUUID().toString().toUpperCase().replace("-","");
            MESSAGE_THREADLOCAL.set(messageId);
        }
        Object result = null;
        try {
            result = joinPoint.proceed();
            printMessageInfo(messageId,exchange, queue,null, messageInfo,MessageStatusEnum.SEND_SUCCESS.getCode(),null);
        } catch (Throwable throwable) {
            printMessageInfo(messageId,exchange, queue,null, messageInfo,MessageStatusEnum.SEND_FAIL.getCode(),throwable);
        }
        return result;
    }

    @Pointcut("execution(* org.springframework.amqp.core.AmqpTemplate.convertAndSend(java.lang.String, java.lang.Object))")
    private void pointSend2(){
    }

    @Around("pointSend2()")
    public Object aroundSend2(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] params = joinPoint.getArgs();
        if(null == params){ return joinPoint.proceed(); }
        if(params.length!=2){ return joinPoint.proceed(); }
        String routeKey = (String) params[0];
        Object message = params[1];
        String messageInfo = JSON.toJSONString(message);
        String messageId = MESSAGE_THREADLOCAL.get();
        if(StringUtils.isBlank(messageId)){
            messageId = UUID.randomUUID().toString().toUpperCase().replace("-","");
            MESSAGE_THREADLOCAL.set(messageId);
        }
        Object result = null;
        try {
            result = joinPoint.proceed();
            printMessageInfo(messageId,null, null,routeKey, messageInfo,MessageStatusEnum.SEND_SUCCESS.getCode(),null);
        } catch (Throwable throwable) {
            printMessageInfo(messageId,null, null,routeKey, messageInfo,MessageStatusEnum.SEND_FAIL.getCode(),throwable);
        }
        return result;
    }

    @Pointcut("execution(* org.springframework.amqp.rabbit.core.RabbitTemplate.convertAndSend(java.lang.String, java.lang.String, java.lang.Object, org.springframework.amqp.rabbit.support.CorrelationData))")
    private void pointSendRabbit(){
    }

    @Around("pointSendRabbit()")
    public Object aroundSendRabbit(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] params = joinPoint.getArgs();
        if(null == params){ return joinPoint.proceed(); }
        if(params.length!=4){ return joinPoint.proceed(); }
        String exchange = (String) params[0];
        String queue = (String) params[1];
        Object message = params[2];
        String messageInfo;
        if(message instanceof String){
            messageInfo = (String) message;
        }else{
            messageInfo = JSON.toJSONString(message);
        }
        String messageId = MESSAGE_THREADLOCAL.get();
        if(StringUtils.isBlank(messageId)){
            messageId = UUID.randomUUID().toString().toUpperCase().replace("-","");
            MESSAGE_THREADLOCAL.set(messageId);
        }
        Object result = null;
        try {
            result = joinPoint.proceed();
            printMessageInfo(messageId,exchange, queue,null, messageInfo,MessageStatusEnum.SEND_SUCCESS.getCode(),null);
        } catch (Throwable throwable) {
            printMessageInfo(messageId,exchange, queue,null, messageInfo,MessageStatusEnum.SEND_FAIL.getCode(),throwable);
        }
        return result;
    }

    @Pointcut("execution(* org.springframework.amqp.core.MessageListener.onMessage(org.springframework.amqp.core.Message))")
    private void pointReceive(){
    }

    @Around("pointReceive()")
    public Object aroundReceive(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] params = joinPoint.getArgs();
        Message message = null;
        for (Object param : params){
            if(param instanceof Message){
                message = (Message) param;
            }
        }
        if( null == message ){ return joinPoint.proceed(); }
        printMessageInfo(message, MessageStatusEnum.RECEIVED.getCode(), null);
        Object result = null;
        try {
            result = joinPoint.proceed();
            printMessageInfo(message, MessageStatusEnum.CONSUMPTION_SUCCESS.getCode(), null);
        } catch (Throwable throwable) {
            printMessageInfo(message, MessageStatusEnum.CONSUMPTION_FAIL.getCode(),throwable);
        }
        return result;
    }

    @Pointcut("@annotation(org.springframework.amqp.rabbit.annotation.RabbitListener)")
    private void pointReceiveAware(){
    }

    @Around("pointReceiveAware()")
    public Object aroundReceiveAware(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] params = joinPoint.getArgs();
        Message message = null;
        for (Object param : params){
            if(param instanceof Message){
                message = (Message) param;
            }
        }
        if( null == message ){ return joinPoint.proceed(); }
        printMessageInfo(message, MessageStatusEnum.RECEIVED.getCode(), null);
        Object result = null;
        try {
            result = joinPoint.proceed();
            printMessageInfo(message, MessageStatusEnum.CONSUMPTION_SUCCESS.getCode(), null);
        } catch (Throwable throwable) {
            printMessageInfo(message, MessageStatusEnum.CONSUMPTION_FAIL.getCode(),throwable);
        }
        return result;
    }

    @Pointcut("execution(* org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener.onMessage(..))")
    private void pointReceiveRabbit(){
    }

    @Around("pointReceiveRabbit()")
    public Object aroundReceiveRabbit(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] params = joinPoint.getArgs();
        Message message = null;
        for (Object param : params){
            if(param instanceof Message){
                message = (Message) param;
            }
        }
        if( null == message ){ return joinPoint.proceed(); }
        printMessageInfo(message, MessageStatusEnum.RECEIVED.getCode(), null);
        Object result = null;
        try {
            result = joinPoint.proceed();
            printMessageInfo(message, MessageStatusEnum.CONSUMPTION_SUCCESS.getCode(), null);
        } catch (Throwable throwable) {
            printMessageInfo(message, MessageStatusEnum.CONSUMPTION_FAIL.getCode(),throwable);
        }
        return result;
    }

    private void printMessageInfo(String messageId, String exchange, String queue, String routeKey, String messageInfo, Integer status, Throwable throwable) {
        MessageInfo info = new MessageInfo();
        info.setMessageId(messageId);
        info.setMessageContent(messageInfo);
        info.setExchange(exchange);
        info.setConsumeQueue(queue);
        info.setRouteKey(routeKey);
        info.setStatus(status);
        if(null!=throwable){
            info.setErrorMsg(printStackTrace(throwable));
        }
        info.setCreateTime(DateUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
        String applicationName = System.getProperty("dubbo.application.name");
        info.setSendApplicationName(applicationName);
        info.setSendIp(getLocalIp());
        logger.info("{}", JSON.toJSONString(info));
    }

    private void printMessageInfo(Message message, Integer status, Throwable throwable) {
        if(null == message){ return; }
        String queue = message.getMessageProperties().getConsumerQueue();
        String routeKey = message.getMessageProperties().getReceivedRoutingKey();
        String messageId = message.getMessageProperties().getMessageId();
        String exchange = message.getMessageProperties().getReceivedExchange();
        String messageInfo = new String(message.getBody(), StandardCharsets.UTF_8);

        MessageInfo info = new MessageInfo();
        info.setMessageId(messageId);
        info.setMessageContent(messageInfo);
        info.setExchange(exchange);
        info.setRouteKey(routeKey);
        info.setConsumeQueue(queue);
        info.setStatus(status);
        info.setUpdateTime(DateUtils.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
        String applicationName = System.getProperty("dubbo.application.name");
        info.setConsumeApplicationName(applicationName);
        if(null!=throwable){
            info.setErrorMsg(printStackTrace(throwable));
        }
        info.setConsumeIp(getLocalIp());
        logger.info("{}", JSON.toJSONString(info));
    }

    /**打印堆栈异常**/
    private String printStackTrace(Throwable e) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        PrintStream pout = new PrintStream(out);
        e.printStackTrace(pout);
        String sw = new String(out.toByteArray());
        pout.close();
        try {
            out.close();
        } catch (Exception ex) {
            // do nothing.
        }
        return sw;
    }

    /**获取本机IP**/
    private String getLocalIp(){
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            if(null!=inetAddress){
                return inetAddress.getHostAddress();
            }
        } catch (UnknownHostException e) {
            // do nothing.
        }
        return null;
    }
}
